(2024/04/06更新) 因應React在18後更新了許多不同的語法,更新後的教學之後將陸續放在 新的blog 中,歡迎讀者到該處閱讀,我依然會回覆這邊的提問
人的一生的週期,會從出生、長大到死亡。出生又可以分為胚胎期、生出來那一瞬間、生出來後,長大也是一樣可以繼續往下區分...。
和人的一生很像,對於大部分的GUI框架,「改變(渲染)畫面」這個動作背後其實都有一套流程(週期)。而React設計了一套特別的component生命週期函數,在這三個情境建構了不同的生命週期:
元件被安裝時(Mount)、元件被更新時(Update)、元件被移除時(Unmount)
每個週期也是和出生一樣可以區分,用一或多個函式隔成不同階段。例如你可以定義元件安裝前、安裝後要做什麼事,就像你可能會希望孩子誕生前幫他買個嬰兒床,健康的誕生,然後要他生出來之後叫你一聲爸以滿足你多年的夢想(?)
蛤?你到底在說什麼? js丟到畫面上可以work不就好了嗎?為什麼安裝前要做事情?安裝完還要幹嘛?
這就是為什麼我把生命週期拆成4篇來講,我希望能針對常用的生命週期,去講他們是怎麼被使用,之後Day19會放一張統整的流程圖,等我發到那裡的時候可以去搭配使用。
請注意,這四篇所講的生命週期函數只能在class component使用。有關function component的生命週期使用之後會特別講
我們先從安裝開始講起。
前面有提過,React component渲染畫面前最後一個呼叫函式是render()
函式。實際上在第一次渲染之前,React還會依序呼叫兩個函式: constructor()和componentWillMount()。也就是第一次render前的流程是:
constructor() -> componentWillMount() -> render() -> 渲染DOM ->......(渲染後的生命週期)
在使用了一段時間後,大家發現componentWillMount()產生了一些問題,不被建議使用。因此在version 17後,componentWillMount()將會被改為UNSAFE_componentWillMount(),以Ver.16.3誕生的新週期函數static getDerivedStateFromProps()
來代替。
也就是新的Mount週期在render
前的流程是為:
constructor() -> static getDerivedStateFromProps() -> render() -> 渲染DOM -> ......(渲染後的生命週期)
在這篇,我還是會講一下有關componentWillMount()的使用。一者是因為目前很多網路上的資源還是以舊的生命週期為主,避免讀者在查相關資料時疑惑。另外一個原因是在我撰寫這篇的當下新舊生命函數都是可以使用的,且componentWillMount()未來也會以UNSAFE_componentWillMount()形式存在。
這個比較不用特別說,過去class component就已經使用過了,就是資料的宣告、初始化、預備、函式綁定等等等......
因為state和props在constructor才被創造,這個函數最常使用的狀況是「用初始接收到的props」去設定第一次render時的state或是做其他的事情。注意這個函數是static,也就是this不能在這裡使用(static指的是這函式不屬於以這個class被宣告出來的單一物件,而是泛屬於此class類別)。
因此props和state改用此函式接收的參數去讀取,並且我們不能在這邊呼叫this.setState
,更改state的方法是用預寫好的規則 : 以這個函式的return值來設定
static getDerivedStateFromProps(props,state){
if(props.dad!=="Chang")
return {isRightDad:false}
}
在這裡如果我們直接或間接對state/props更改,不會因為state/props改變而重複呼叫render函式。在這邊,我建立了一個Baby.js來說明:
import React, { Component } from 'react';
class Baby extends Component{
constructor(props) {
super(props);
this.state={
isRightDad: true
}
}
static getDerivedStateFromProps(props,state){
if(props.dad!=="Chang")
return {isRightDad:false}
}
render(){
if(this.state.isRightDad===true)
return(
<div>
他的媽媽,是小美
</div>
);
else
return(
<div>
他的媽媽,是誰,干你X事
</div>
);
}
}
export default Baby;
引入在index.js中
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import Baby from './Baby'
import * as serviceWorker from './serviceWorker';
ReactDOM.render(
<div>
<Baby dad="Chang"/>
</div>,
document.getElementById('root')
);
serviceWorker.unregister();
如果初始時得知爸爸不是張先生,把isRightDad設定為false,render不會告訴他孩子的媽是誰。
要注意的是,我們不希望在getDerivedStateFromProps()中做宣告/初始化的動作,如果可以的話就在constructor中做,其他如fetch或是動畫等,應該移到下一篇要講的componentDidMount()
中來做。
它不是static,在裡面定義、使用函式方法跟一般函式差不多。
componentWillMount(){
if(this.props.dad!=="Chang")
this.setState({isRightDad:false})
}
過去除了getDerivedStateFromProps()
的功能外,很多人會在這裡執行fetch以取得想在render()
中使用的資料。例如token的檢查等等。
聽起來「在這裡執行fetch,等資料接到了再render」很合理。然而官方表示,如果在componentWillMount()
這執行fetch,並不會等response進來才執行 render
。又因為這是唯一會在 *server side (見註解)執行的生命週期函數,導致它在server side和client side都會執行一次,「重複執行」這件事並不符合我們對Mount週期函數的期待。
version 17後,componentWillMount()
將會被改為UNSAFE_componentWillMount()
。
*註解: 推薦看這兩篇來了解SSR(server side rendering)
(SSR) Server-Side Rendering with Angular
[React] SSR 筆記
就是收集要渲染到畫面上的東西,前面都用過了。
要注意的是render()只是渲染前最後一個呼叫的生命週期函數,元件還沒有真的渲染到DOM上。所以不要在render()中操作有關return元素的DOM。
一般我們對Mount系列函數的期待是「只執行一次」,不想重複執行的動作都會在這系列呼叫。
那麼從上一篇一直講到現在,fetch到底應該要在哪裡執行呢?
答案就在下一篇要講的componentDidMount()
。
過去在看大家對於React的討論,最常被提到的就是生命週期這東西並不是很多人真正的了解。
坦白說,我也不認為自己很熟悉這一塊,自己目前做過的專案也只有碰到部分的函數。如果有錯誤的地方,希望各位前輩能指點~